﻿using System;
using System.Collections.Generic;
using System.Text;
using IAnalyze;
using System.Drawing;
using System.Drawing.Imaging;
using System.Diagnostics;
namespace SmokeFireAnalyze
{
    public class SmokeFireAnalyze : IAnalyze.IAnalyze
    {
        byte[,] StableBackground;                   //背景建模
        readonly int fitness = 15;                  //背景适应度，该值越大，适应越慢
        readonly int motionThreshold = 50;          //运动点阈值，视频帧像素值与背景帧像素的差值绝对值大于此阈值，即为运动点
        readonly float regionThreshold = 0.1f;      //运动区域百分比阈值，当区域中运动点的百分比大于此阈值时，该区域为运动区域
        readonly int edgeThreshold = 30;            //灰度差分阈值，即边界阈值
        readonly float smokeThreshold = 0.01f;       //运动区域中边界点所占百分比阈值，当边界点所占百分比小于此阈值时为烟
        readonly int rectThreshold = 6;             //报警区域数量阈值，报警区域数量小于此阈值时才产生报警
        readonly Size sizeMax = new Size(200, 200);
        readonly Size sizeMin = new Size(10, 10);

        #region IAnalyze 成员

        //报警框最大纹理标记数
        const int RectMaxTextureCount = 10;

        //是否显示被纹理过滤的报警框内的纹理(纹理用黄点标记)
        const bool IsShowRectTexture = false;
        bool isReset = true;
        /// <summary>
        /// 复位
        /// </summary>
        public void Reset()
        {
            isReset = true;
        }

        //背景建模
        private unsafe object[] BackgroundModeling(byte* pIn, byte* pOut, Size sizeIn, Size sizeOut)
        {
            if (StableBackground == null)
                StableBackground = new byte[sizeOut.Width, sizeOut.Height];

            for (int yOut = 0; yOut < sizeOut.Height; yOut++)
            {
                for (int xOut = 0; xOut < sizeOut.Width; xOut++)
                {
                    int index = (yOut * sizeOut.Width + xOut) * 3;
                    byte key = pIn[index];

                    byte maxKey = StableBackground[xOut, yOut];

                    if (maxKey == 0)
                        StableBackground[xOut, yOut] = key;
                    else if (key > maxKey)
                        StableBackground[xOut, yOut] += (byte)((key - maxKey + 14) / 15);
                    else if (key < maxKey)
                        StableBackground[xOut, yOut] -= (byte)((maxKey - key + 14) / 15);

                    pOut[index] =
                    pOut[index + 1] =
                    pOut[index + 2] =
                    Math.Min(Math.Max(StableBackground[xOut, yOut], byte.MinValue), byte.MaxValue);
                }
            }

            return new object[] { StableBackground };

        }

        //背景差分
        private unsafe Rectangle[] BackgroundSubtract(byte* pIn, byte* pOut, Size sizeIn, Size sizeOut)
        {
            #region 运动点标记
            for (int yOut = 0; yOut < sizeOut.Height; yOut++)
            {
                for (int xOut = 0; xOut < sizeOut.Width; xOut++)
                {
                    int index = (yOut * sizeOut.Width + xOut) * 3;
                    pOut[index] =
                    (byte)(Math.Abs(StableBackground[xOut, yOut]
                    - pIn[(yOut * sizeOut.Width + xOut) * 3]) > 30 ? 255 : 0);
                    pOut[index + 1] = pOut[index + 2] = 0;

                }
            }
            #endregion

            ////获取运动点所在区域
            List<Rectangle> rectActive = GetExpandRegion(pOut, sizeOut, sizeMin, sizeMax);
            //合并重叠区域
            UnionRectangle(ref rectActive, new Size(0, 0));
            //过滤边缘区域
            Rectangle rectLimits = new Rectangle(new Point(), sizeOut);
            rectLimits.Inflate(-50, -50);
            List<Rectangle> rectContain = ContainRectangle(rectActive, rectLimits);
            //获取最大区域
            // Rectangle rectMax = DataManager.MaxRectangle(rectContain);
            foreach (var rect in rectContain)
            {
                DrawRectangle(rect, Color.Lime, pOut, sizeOut);
            }

            return rectActive.ToArray();
        }

        //根据二维坐标获取一维坐标，通道数为3
        public static int GetIndex(int x, int y, int w) { return (y * w + x) * 3; }

        //获取一组将区域中连续点包含在其中的矩形框
        unsafe public static List<Rectangle> GetExpandRegion(byte* pOut, Size imageOut, Size sizeMin, Size sizeMax)
        {
            List<Rectangle> rectActive = new List<Rectangle>();
            #region 矩形扩张
            for (int yOut = 0; yOut < imageOut.Height; yOut++)
            {
                for (int xOut = 0; xOut < imageOut.Width; xOut++)
                {
                    int index = GetIndex(xOut, yOut, imageOut.Width);
                    if (pOut[index] == 255)
                    {
                        Rectangle rect = new Rectangle(xOut, yOut, 1, 1);
                        //向右延伸
                        for (int i = rect.Right; i < imageOut.Width; i++)
                        {
                            if (pOut[GetIndex(i, yOut, imageOut.Width)] == 0)
                            {
                                rect.Width = i - xOut;
                                break;
                            }
                        }
                        //向下延伸
                        for (int i = rect.Bottom; i < imageOut.Height; i++)
                        {
                            int count = 0;
                            for (int j = rect.Left; j < rect.Right; j++)
                            {
                                if (pOut[GetIndex(j, i, imageOut.Width)] > 0)
                                {
                                    count++;
                                    break;
                                }
                            }
                            if (count == 0)
                            {
                                rect.Height = i - yOut;
                                break;
                            }
                        }
                        //向左延伸
                        for (int i = rect.Left - 1; i > 0; i--)
                        {
                            int count = 0;
                            for (int j = rect.Top; j < rect.Bottom; j++)
                            {
                                if (pOut[GetIndex(i, j, imageOut.Width)] > 0)
                                {
                                    count++;
                                    break;
                                }
                            }
                            if (count == 0)
                            {
                                rect.X = i;
                                rect.Width += rect.Left - i;
                                break;
                            }
                        }
                        //向右延伸
                        for (int i = rect.Right; i < imageOut.Width; i++)
                        {
                            int count = 0;
                            for (int j = rect.Top; j < rect.Bottom; j++)
                            {
                                if (pOut[GetIndex(i, j, imageOut.Width)] > 0)
                                {
                                    count++;
                                    break;
                                }
                            }
                            if (count == 0)
                            {
                                rect.Width = i - rect.Left;
                                break;
                            }
                        }
                        //向下延伸
                        for (int i = rect.Bottom; i < imageOut.Height; i++)
                        {
                            int count = 0;
                            for (int j = rect.Left; j < rect.Right; j++)
                            {
                                if (pOut[GetIndex(j, i, imageOut.Width)] > 0)
                                {
                                    count++;
                                    break;
                                }
                            }
                            if (count == 0)
                            {
                                rect.Height = i - yOut;
                                break;
                            }
                        }

                        if (rect.Width > sizeMin.Width
                            && rect.Height > sizeMin.Height
                            && rect.Width < sizeMax.Width
                            && rect.Height < sizeMax.Height)
                        {
                            rectActive.Add(rect);

                            for (int y = rect.Top; y < rect.Bottom; y++)
                            {
                                for (int x = rect.Left; x < rect.Right; x++)
                                {
                                    //标记运动的点,下次扫描则不再检测该点
                                    int idx = GetIndex(x, y, imageOut.Width);
                                    if (pOut[idx] == 255)
                                    {
                                        pOut[idx] = 0;
                                        pOut[idx + 1] = 255;
                                    }

                                }
                            }
                        }


                    }
                }
            }
            #endregion
            return rectActive;
        }

        //合并重叠矩形
        public static void UnionRectangle(ref List<Rectangle> rects, Size inflate)
        {
            bool intersectExsit = false;//没有一个相交
            #region 合并相交矩形
            do
            {
                intersectExsit = false;
                for (int i = 0; i < rects.Count - 1; i++)
                {
                    bool intersect = false;//是否与其它相交
                    for (int j = i + 1; j < rects.Count; j++)
                    {
                        Rectangle rect1 = rects[i];
                        Rectangle rect2 = rects[j];
                        rect1.Inflate(inflate);
                        rect2.Inflate(inflate);
                        if (rect1.IntersectsWith(rect2))
                        {
                            rect1.Inflate(-inflate.Width, -inflate.Height);
                            rect2.Inflate(-inflate.Width, -inflate.Height);
                            rects.Remove(rect1);//将重叠的移除
                            rects.Remove(rect2);//将重叠的移除
                            rects.Add(Rectangle.Union(rect1, rect2));//加入合并后的矩形
                            intersect = true;
                            break;
                        }
                    }
                    if (intersect)
                    {
                        intersectExsit = true;
                        break;
                    }
                }

            } while (intersectExsit);
            #endregion
        }

        //获取一组矩形中被包含在指定矩形区域内的矩形
        public static List<Rectangle> ContainRectangle(List<Rectangle> rects, Rectangle rectLimits)
        {
            List<Rectangle> rectContain = new List<Rectangle>();
            foreach (var rect in rects)
            {
                if (rectLimits.Contains(rect)) rectContain.Add(rect);
            }
            return rectContain;
        }

        //二维字节数组上绘制矩形
        unsafe public static void DrawRectangle(Rectangle rect, Color color, byte* pBuffer, Size sizeBuffer)
        {
            for (int i = rect.Left; i < rect.Right; i++)
            {
                int indexTop = GetIndex(i, rect.Top, sizeBuffer.Width);
                int indexBottom = GetIndex(i, rect.Bottom - 1, sizeBuffer.Width);
                pBuffer[indexTop + 0] = color.B;
                pBuffer[indexTop + 1] = color.G;
                pBuffer[indexTop + 2] = color.R;
                pBuffer[indexBottom + 0] = color.B;
                pBuffer[indexBottom + 1] = color.G;
                pBuffer[indexBottom + 2] = color.R;
            }

            for (int i = rect.Top; i < rect.Bottom; i++)
            {
                int indexLeft = GetIndex(rect.Left, i, sizeBuffer.Width);
                int indexRight = GetIndex(rect.Right - 1, i, sizeBuffer.Width);
                pBuffer[indexLeft + 0] = color.B;
                pBuffer[indexLeft + 1] = color.G;
                pBuffer[indexLeft + 2] = color.R;
                pBuffer[indexRight + 0] = color.B;
                pBuffer[indexRight + 1] = color.G;
                pBuffer[indexRight + 2] = color.R;
            }
        }

        Random r = new Random();//随机生成报警（仅供测试使用，请勿删除！）
        public bool Analyze(Bitmap image, out Rectangle[] region, out int[] regionType)
        {
            //#region 随机生成报警（仅供测试使用，请勿删除！）
            //if (r.Next(100) > 90)
            //{
            //    int count = r.Next(1, 6);
            //    region = new Rectangle[count];
            //    regionType = new int[count];

            //    for (int i = 0; i < count; i++)
            //    {
            //        int x = r.Next(image.Width - 10);
            //        int w = r.Next(image.Width - x);
            //        int y = r.Next(image.Height - 10);
            //        int h = r.Next(image.Height - y);
            //        region[i] = new Rectangle(x, y, w, h);
            //        regionType[i] = r.Next(1, 7);
            //    }

            //    return true;
            //}
            //else
            //{
            //    region = null;
            //    regionType = null;
            //    return false;
            //}
            //#endregion

            bool isSmoke = false;
            Size sizeOut = image.Size;
            List<Rectangle> rectSmoke = new List<Rectangle>();

            Bitmap imageOut = new Bitmap(image.Width, image.Height);
            BitmapData dataIn = image.LockBits(new Rectangle(new Point(), image.Size), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            BitmapData dataOut = imageOut.LockBits(new Rectangle(new Point(), image.Size), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            unsafe
            {
                byte* pIn = (byte*)dataIn.Scan0.ToPointer();
                byte* pOut = (byte*)dataOut.Scan0.ToPointer();

                //背景初始化
                if (isReset)
                {
                    if (StableBackground == null) StableBackground = new byte[sizeOut.Width, sizeOut.Height];

                    for (int yOut = 0; yOut < sizeOut.Height; yOut++)
                    {
                        for (int xOut = 0; xOut < sizeOut.Width; xOut++)
                        {
                            int index = GetIndex(xOut, yOut, sizeOut.Width);
                            StableBackground[xOut, yOut] = pIn[index];
                        }
                    }

                    isReset = false;
                }
                else
                {
                    //自适应背景建模
                    for (int yOut = 0; yOut < sizeOut.Height; yOut++)
                    {
                        for (int xOut = 0; xOut < sizeOut.Width; xOut++)
                        {
                            int index = GetIndex(xOut, yOut, sizeOut.Width);
                            byte key = pIn[index];

                            byte back = StableBackground[xOut, yOut];

                            if (key > back)
                                StableBackground[xOut, yOut] += (byte)((key - back + fitness - 1) / fitness);
                            else if (key < back)
                                StableBackground[xOut, yOut] -= (byte)((back - key + fitness - 1) / fitness);

                            ////测试，背景模型
                            //pIn[index] = pIn[index + 1] = pIn[index + 2] = StableBackground[xOut, yOut];
                        }
                    }

                    //背景差分，获取运动像素点
                    for (int yOut = 0; yOut < sizeOut.Height; yOut++)
                    {
                        for (int xOut = 0; xOut < sizeOut.Width; xOut++)
                        {
                            int index = GetIndex(xOut, yOut, sizeOut.Width);
                            pOut[index] =
                            (byte)(Math.Abs(StableBackground[xOut, yOut]
                            - pIn[index]) > this.motionThreshold ? 255 : 0);
                            pOut[index + 1] = pOut[index + 2] = 0;

                            ////测试，差分图像
                            //pIn[index] = pIn[index + 1] = pIn[index + 2] = pOut[index];
                        }
                    }

                    //获取运动像素点所在区域
                    //List<Rectangle> rectActive = GetExpandRegion(pOut, sizeOut, new Size(10, 10), new Size(100, 100));
                    List<Rectangle> rectActive = new List<Rectangle>();
                    Size sizeSubWindow = new Size(16, 16);
                    for (int yOut = 0; yOut < sizeOut.Height; yOut += sizeSubWindow.Height)
                    {
                        for (int xOut = 0; xOut < sizeOut.Width; xOut += sizeSubWindow.Width)
                        {
                            int countMotion = 0;
                            for (int y = 0; y < sizeSubWindow.Height; y++)
                            {
                                for (int x = 0; x < sizeSubWindow.Width; x++)
                                {
                                    int index = GetIndex(xOut + x, yOut + y, sizeOut.Width);
                                    if (pOut[index] == 255) countMotion++;
                                }
                            }
                            if (countMotion > sizeSubWindow.Width * sizeSubWindow.Height * this.regionThreshold)
                                rectActive.Add(new Rectangle(xOut, yOut, sizeSubWindow.Width, sizeSubWindow.Height));
                        }
                    }

                    ////测试，标记运动区域
                    //foreach (var item in rectActive)
                    //{
                    //    DrawRectangle(item, Color.Red, pIn, sizeOut);
                    //}

                    //合并重叠运动区域
                    UnionRectangle(ref rectActive, new Size(3, 3));

                    ////测试，标记合并区域
                    //foreach (var item in rectActive)
                    //{
                    //    DrawRectangle(item, Color.Blue, pIn, sizeOut);
                    //}

                    foreach (var rect in rectActive)
                    {
                        int count = 0;
                        for (int yOut = rect.Top; yOut < rect.Bottom - 1; yOut++)
                        {
                            for (int xOut = rect.Left + 1; xOut < rect.Right - 1; xOut++)
                            {
                                int[] indexes = new int[5];
                                indexes[0] = GetIndex(xOut, yOut, sizeOut.Width);
                                indexes[1] = GetIndex(xOut + 1, yOut, sizeOut.Width);
                                indexes[2] = GetIndex(xOut, yOut + 1, sizeOut.Width);
                                indexes[3] = GetIndex(xOut + 1, yOut + 1, sizeOut.Width);
                                indexes[4] = GetIndex(xOut - 1, yOut + 1, sizeOut.Width);

                                for (int i = 1; i < 5; i++)
                                {
                                    //统计纹理点
                                    if (pIn[indexes[0]] - pIn[indexes[i]] > this.edgeThreshold)
                                    {
                                        //测试，标记轮廓点
                                        pIn[indexes[0]] = 0;
                                        pIn[indexes[0] + 1] = 255;
                                        pIn[indexes[0] + 2] = 0;

                                        count++;
                                        break;
                                    }

                                }
                            }
                        }
                        if (count < rect.Width * rect.Height * this.smokeThreshold)//按纹理点过滤
                            rectSmoke.Add(rect);
                    }

                    ////测试，标记烟雾区域
                    //foreach (var item in rectSmoke)
                    //{
                    //    DrawRectangle(item, Color.Yellow, pIn, sizeOut);
                    //}

                    if (rectSmoke.Count > 0 && rectSmoke.Count <= this.rectThreshold)//按报警区域数量过滤
                    {
                        isSmoke = true;
                    }
                    else
                    {
                        isSmoke = false;
                    }

                }
            }

            image.UnlockBits(dataIn);
            imageOut.UnlockBits(dataOut);
            imageOut.Dispose();

            if (isSmoke)
            {
                region = rectSmoke.ToArray();
                regionType = new int[rectSmoke.Count];
                for (int i = 0; i < regionType.Length; i++)
                {
                    regionType[i] = (int)AlarmRegionEnum.Smoke;
                }
            }
            else
            {
                region = null;
                regionType = null;
            }

            rectSmoke.Clear();

            return isSmoke;

            ////测试
            //region = new Rectangle[0];
            //regionType = new int[0];
            //return true;
        }

        #endregion
    }
}
